<!doctype html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>JavaScript 投掷动画模拟</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
      color: white;
      padding: 20px;
      overflow-x: hidden;
    }

    .container {
      width: 100%;
      max-width: 900px;
      text-align: center;
    }

    h1 {
      margin-bottom: 15px;
      font-size: 2.5rem;
      text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
    }

    .description {
      margin-bottom: 25px;
      font-size: 1.1rem;
      line-height: 1.6;
      max-width: 700px;
      margin-left: auto;
      margin-right: auto;
    }

    .scene {
      position: relative;
      width: 100%;
      height: 450px;
      background-color: rgba(255, 255, 255, 0.1);
      border-radius: 12px;
      margin-bottom: 20px;
      overflow: hidden;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
      border: 1px solid rgba(255, 255, 255, 0.2);
    }

    .thrower {
      position: absolute;
      bottom: 50px;
      left: 100px;
      width: 80px;
      height: 150px;
      z-index: 5;
    }

    .thrower-body {
      position: absolute;
      width: 60px;
      height: 100px;
      background: linear-gradient(to bottom, #8b4513, #a0522d);
      border-radius: 30px 30px 0 0;
      bottom: 0;
      left: 10px;
    }

    .thrower-head {
      position: absolute;
      width: 50px;
      height: 50px;
      background: linear-gradient(45deg, #ffdab9, #f4a460);
      border-radius: 50%;
      bottom: 100px;
      left: 15px;
    }

    .thrower-arm {
      position: absolute;
      width: 15px;
      height: 70px;
      background: linear-gradient(to bottom, #8b4513, #a0522d);
      border-radius: 10px;
      bottom: 60px;
      left: 70px;
      transform-origin: bottom center;
      transition: transform 0.3s ease-out;
    }

    .object {
      position: absolute;
      width: 40px;
      height: 40px;
      background: linear-gradient(45deg, #ff416c, #ff4b2b);
      border-radius: 50%;
      cursor: grab;
      display: flex;
      align-items: center;
      justify-content: center;
      color: white;
      font-weight: bold;
      user-select: none;
      box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
      transition: transform 0.2s;
      z-index: 10;
      left: 120px;
      bottom: 130px;
    }

    .object:active {
      cursor: grabbing;
      transform: scale(1.1);
    }

    .controls {
      display: flex;
      gap: 15px;
      margin-bottom: 20px;
      flex-wrap: wrap;
      justify-content: center;
    }

    button {
      padding: 10px 20px;
      background: rgba(255, 255, 255, 0.2);
      border: none;
      border-radius: 50px;
      color: white;
      font-size: 1rem;
      cursor: pointer;
      transition: all 0.3s;
      backdrop-filter: blur(5px);
      border: 1px solid rgba(255, 255, 255, 0.3);
    }

    button:hover {
      background: rgba(255, 255, 255, 0.3);
      transform: translateY(-2px);
    }

    .stats {
      display: flex;
      justify-content: center;
      gap: 30px;
      margin-top: 20px;
      flex-wrap: wrap;
    }

    .stat-item {
      background: rgba(255, 255, 255, 0.1);
      padding: 15px 25px;
      border-radius: 10px;
      min-width: 150px;
      backdrop-filter: blur(5px);
      border: 1px solid rgba(255, 255, 255, 0.2);
    }

    .stat-value {
      font-size: 1.5rem;
      font-weight: bold;
      margin-top: 5px;
      color: #ffd700;
    }

    .instructions {
      margin-top: 20px;
      font-style: italic;
      opacity: 0.8;
      max-width: 600px;
    }

    .trail {
      position: absolute;
      width: 6px;
      height: 6px;
      background-color: rgba(255, 215, 0, 0.7);
      border-radius: 50%;
      pointer-events: none;
      z-index: 2;
    }

    .ground {
      position: absolute;
      bottom: 0;
      left: 0;
      width: 100%;
      height: 50px;
      background: linear-gradient(to top, #8b4513, #a0522d);
      z-index: 1;
    }

    .target {
      position: absolute;
      bottom: 50px;
      right: 150px;
      width: 80px;
      height: 80px;
      background: radial-gradient(circle, #ff0000, #8b0000);
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      color: white;
      font-weight: bold;
      box-shadow: 0 0 20px rgba(255, 0, 0, 0.5);
      z-index: 3;
    }

    .target::before,
    .target::after {
      content: "";
      position: absolute;
      width: 60px;
      height: 4px;
      background: white;
      border-radius: 2px;
    }

    .target::before {
      transform: rotate(45deg);
    }

    .target::after {
      transform: rotate(-45deg);
    }

    @media (max-width: 600px) {
      h1 {
        font-size: 1.8rem;
      }

      .scene {
        height: 400px;
      }

      .stats {
        flex-direction: column;
        align-items: center;
      }

      .thrower {
        left: 50px;
      }

      .target {
        right: 80px;
      }
    }
  </style>
</head>

<body>
  <div class="container">
    <h1>JavaScript 投掷动画模拟</h1>
    <p class="description">
      拖动投掷物并释放，观察在重力作用下的抛物线运动轨迹。尝试击中右侧的靶心！
    </p>

    <div class="controls">
      <button id="resetBtn">重置投掷物</button>
      <button id="toggleTrailBtn">切换轨迹显示</button>
      <button id="gravityBtn">切换重力方向</button>
      <button id="autoThrowBtn">自动投掷演示</button>
    </div>

    <div class="scene" id="scene">
      <div class="ground"></div>
      <div class="thrower">
        <div class="thrower-body"></div>
        <div class="thrower-head"></div>
        <div class="thrower-arm" id="throwerArm"></div>
      </div>
      <div class="object" id="object">投掷物</div>
      <div class="target">靶心</div>
    </div>

    <div class="stats">
      <div class="stat-item">
        <div>水平速度</div>
        <div class="stat-value" id="velocityX">0 m/s</div>
      </div>
      <div class="stat-item">
        <div>垂直速度</div>
        <div class="stat-value" id="velocityY">0 m/s</div>
      </div>
      <div class="stat-item">
        <div>投掷次数</div>
        <div class="stat-value" id="throwCount">0</div>
      </div>
      <div class="stat-item">
        <div>命中次数</div>
        <div class="stat-value" id="hitCount">0</div>
      </div>
    </div>

    <p class="instructions">
      提示：尝试不同的拖动速度和角度，观察抛物线轨迹的变化。快速拖动会产生更大的初速度！
    </p>
  </div>

  <script>
    document.addEventListener("DOMContentLoaded", function () {
      const object = document.getElementById("object");
      const scene = document.getElementById("scene");
      const throwerArm = document.getElementById("throwerArm");
      const resetBtn = document.getElementById("resetBtn");
      const toggleTrailBtn = document.getElementById("toggleTrailBtn");
      const gravityBtn = document.getElementById("gravityBtn");
      const autoThrowBtn = document.getElementById("autoThrowBtn");
      const velocityXEl = document.getElementById("velocityX");
      const velocityYEl = document.getElementById("velocityY");
      const throwCountEl = document.getElementById("throwCount");
      const hitCountEl = document.getElementById("hitCount");
      const target = document.querySelector(".target");

      // 物理参数
      let gravity = 0.5; // 重力加速度
      let damping = 0.7; // 碰撞阻尼系数
      let airResistance = 0.99; // 空气阻力系数
      let showTrail = true; // 是否显示轨迹

      // 状态变量
      let isDragging = false;
      let isThrown = false;
      let throwCount = 0;
      let hitCount = 0;
      let velocityX = 0;
      let velocityY = 0;
      let lastX = 0;
      let lastY = 0;
      let lastTime = 0;
      let gravityDirection = 1; // 1表示向下，-1表示向上
      let animationId = null;

      // 投掷物初始位置（在投掷者手中）
      const initialPosition = {
        x: 120,
        y: scene.offsetHeight - 180,
      };

      // 设置投掷物初始位置
      object.style.left = initialPosition.x + "px";
      object.style.bottom = initialPosition.y + "px";

      // 鼠标/触摸事件处理
      object.addEventListener("mousedown", startDrag);
      object.addEventListener("touchstart", startDrag);

      document.addEventListener("mousemove", drag);
      document.addEventListener("touchmove", drag);

      document.addEventListener("mouseup", endDrag);
      document.addEventListener("touchend", endDrag);

      // 按钮事件
      resetBtn.addEventListener("click", resetObject);
      toggleTrailBtn.addEventListener("click", toggleTrail);
      gravityBtn.addEventListener("click", toggleGravity);
      autoThrowBtn.addEventListener("click", autoThrow);

      // 开始拖动
      function startDrag(e) {
        e.preventDefault();
        isDragging = true;
        isThrown = false;
        object.style.cursor = "grabbing";

        // 停止任何现有的动画
        if (animationId) {
          cancelAnimationFrame(animationId);
          animationId = null;
        }

        // 记录初始位置和时间
        lastX = e.clientX || e.touches[0].clientX;
        lastY = e.clientY || e.touches[0].clientY;
        lastTime = Date.now();

        // 更新投掷者手臂角度
        updateArmAngle();
      }

      // 拖动中
      function drag(e) {
        if (!isDragging) return;

        e.preventDefault();
        const clientX = e.clientX || e.touches[0].clientX;
        const clientY = e.clientY || e.touches[0].clientY;

        // 计算投掷物新位置
        const rect = scene.getBoundingClientRect();
        let newX = parseInt(object.style.left) + (clientX - lastX);
        let newY = parseInt(object.style.bottom) - (clientY - lastY); // 注意Y轴方向

        // 限制投掷物在场景内
        newX = Math.max(
          0,
          Math.min(newX, scene.offsetWidth - object.offsetWidth),
        );
        newY = Math.max(
          0,
          Math.min(newY, scene.offsetHeight - object.offsetHeight - 50),
        ); // 减去地面高度

        object.style.left = newX + "px";
        object.style.bottom = newY + "px";

        // 计算速度（用于投掷）
        const currentTime = Date.now();
        const deltaTime = currentTime - lastTime;

        if (deltaTime > 0) {
          velocityX = ((clientX - lastX) / deltaTime) * 100;
          velocityY = (-(clientY - lastY) / deltaTime) * 100; // Y轴方向取反

          updateStats();
        }

        lastX = clientX;
        lastY = clientY;
        lastTime = currentTime;

        // 更新投掷者手臂角度
        updateArmAngle();
      }

      // 更新投掷者手臂角度
      function updateArmAngle() {
        const objectRect = object.getBoundingClientRect();
        const throwerRect = throwerArm.getBoundingClientRect();

        const dx =
          objectRect.left +
          objectRect.width / 2 -
          (throwerRect.left + throwerRect.width / 2);
        const dy =
          throwerRect.top +
          throwerRect.height / 2 -
          (objectRect.top + objectRect.height / 2);

        const angle = (Math.atan2(dy, dx) * 180) / Math.PI;
        throwerArm.style.transform = `rotate(${angle}deg)`;
      }

      // 结束拖动（投掷）
      function endDrag() {
        if (!isDragging) return;

        isDragging = false;
        isThrown = true;
        object.style.cursor = "grab";

        // 增加投掷计数
        throwCount++;
        throwCountEl.textContent = throwCount;

        // 开始动画
        animate();
      }

      // 动画循环
      function animate() {
        if (!isThrown) return;

        // 应用重力
        velocityY -= gravity * gravityDirection; // Y轴方向取反

        // 应用空气阻力
        velocityX *= airResistance;
        velocityY *= airResistance;

        // 计算新位置
        let newX = parseInt(object.style.left) + velocityX;
        let newY = parseInt(object.style.bottom) - velocityY; // Y轴方向取反

        // 边界碰撞检测
        let hitBoundary = false;

        if (newX <= 0 || newX >= scene.offsetWidth - object.offsetWidth) {
          velocityX = -velocityX * damping; // 水平方向反弹
          newX = newX <= 0 ? 0 : scene.offsetWidth - object.offsetWidth;
          hitBoundary = true;
        }

        if (
          newY <= 0 ||
          newY >= scene.offsetHeight - object.offsetHeight - 50
        ) {
          // 减去地面高度
          velocityY = -velocityY * damping; // 垂直方向反弹
          newY =
            newY <= 0 ? 0 : scene.offsetHeight - object.offsetHeight - 50;
          hitBoundary = true;

          // 如果速度很小，停止动画
          if (Math.abs(velocityY) < 0.5 && Math.abs(velocityX) < 0.5) {
            isThrown = false;
            velocityX = 0;
            velocityY = 0;
            updateStats();
            return;
          }
        }

        // 更新位置
        object.style.left = newX + "px";
        object.style.bottom = newY + "px";

        // 检测是否命中靶心
        checkTargetHit();

        // 添加轨迹点
        if (showTrail) {
          addTrailPoint(
            newX + object.offsetWidth / 2,
            scene.offsetHeight - newY - object.offsetHeight / 2,
          );
        }

        updateStats();

        // 继续动画
        animationId = requestAnimationFrame(animate);
      }

      // 检测是否命中靶心
      function checkTargetHit() {
        const objectRect = object.getBoundingClientRect();
        const targetRect = target.getBoundingClientRect();
        const sceneRect = scene.getBoundingClientRect();

        // 转换为场景内的坐标
        const objX =
          objectRect.left - sceneRect.left + object.offsetWidth / 2;
        const objY = objectRect.top - sceneRect.top + object.offsetHeight / 2;
        const targetX =
          targetRect.left - sceneRect.left + target.offsetWidth / 2;
        const targetY =
          targetRect.top - sceneRect.top + target.offsetHeight / 2;

        // 计算距离
        const distance = Math.sqrt(
          Math.pow(objX - targetX, 2) + Math.pow(objY - targetY, 2),
        );

        // 如果距离小于阈值，判定为命中
        if (distance < 40) {
          hitCount++;
          hitCountEl.textContent = hitCount;

          // 靶心命中效果
          target.style.transform = "scale(1.2)";
          target.style.boxShadow = "0 0 30px rgba(255, 255, 0, 0.8)";

          setTimeout(() => {
            target.style.transform = "scale(1)";
            target.style.boxShadow = "0 0 20px rgba(255, 0, 0, 0.5)";
          }, 300);
        }
      }

      // 添加轨迹点
      function addTrailPoint(x, y) {
        const trail = document.createElement("div");
        trail.className = "trail";
        trail.style.left = x + "px";
        trail.style.top = y + "px";
        scene.appendChild(trail);

        // 3秒后淡出并移除轨迹点
        setTimeout(() => {
          trail.style.opacity = "0";
          trail.style.transition = "opacity 1s";
          setTimeout(() => {
            if (trail.parentNode) {
              trail.parentNode.removeChild(trail);
            }
          }, 1000);
        }, 3000);
      }

      // 更新统计数据
      function updateStats() {
        velocityXEl.textContent = velocityX.toFixed(1) + " m/s";
        velocityYEl.textContent = velocityY.toFixed(1) + " m/s";
      }

      // 重置投掷物
      function resetObject() {
        isDragging = false;
        isThrown = false;
        velocityX = 0;
        velocityY = 0;

        // 停止动画
        if (animationId) {
          cancelAnimationFrame(animationId);
          animationId = null;
        }

        object.style.left = initialPosition.x + "px";
        object.style.bottom = initialPosition.y + "px";

        // 重置投掷者手臂角度
        throwerArm.style.transform = "rotate(0deg)";

        // 清除所有轨迹点
        const trails = document.querySelectorAll(".trail");
        trails.forEach((trail) => trail.remove());

        updateStats();
      }

      // 切换轨迹显示
      function toggleTrail() {
        showTrail = !showTrail;
        toggleTrailBtn.textContent = showTrail ? "隐藏轨迹" : "显示轨迹";

        if (!showTrail) {
          const trails = document.querySelectorAll(".trail");
          trails.forEach((trail) => trail.remove());
        }
      }

      // 切换重力方向
      function toggleGravity() {
        gravityDirection = -gravityDirection;
        gravityBtn.textContent =
          gravityDirection === 1 ? "重力向下" : "重力向上";
      }

      // 自动投掷演示
      function autoThrow() {
        resetObject();

        // 设置随机速度和角度
        const angle = Math.random() * 30 + 30; // 30-60度
        const speed = Math.random() * 10 + 15; // 15-25 m/s

        // 计算速度分量
        velocityX = Math.cos((angle * Math.PI) / 180) * speed;
        velocityY = Math.sin((angle * Math.PI) / 180) * speed;

        // 开始投掷
        isThrown = true;
        throwCount++;
        throwCountEl.textContent = throwCount;

        animate();
      }
    });
  </script>
</body>

</html>
